/**
 * Test suite for useFormatTimeFromNow hook
 *
 * This hook provides internationalized relative time formatting (e.g., "2 hours ago", "3 days ago")
 * using dayjs with the relativeTime plugin. It automatically uses the correct locale based on
 * the user's i18n settings.
 *
 * Key features:
 * - Supports 20+ locales with proper translations
 * - Automatically syncs with user's interface language
 * - Uses dayjs for consistent time calculations
 * - Returns human-readable relative time strings
 */
import { renderHook } from '@testing-library/react'
import { useFormatTimeFromNow } from './use-format-time-from-now'

// Mock the i18n context
jest.mock('@/context/i18n', () => ({
  useI18N: jest.fn(() => ({
    locale: 'en-US',
  })),
}))

// Import after mock to get the mocked version
import { useI18N } from '@/context/i18n'

describe('useFormatTimeFromNow', () => {
  beforeEach(() => {
    jest.clearAllMocks()
  })

  describe('Basic functionality', () => {
    /**
     * Test that the hook returns a formatTimeFromNow function
     * This is the primary interface of the hook
     */
    it('should return formatTimeFromNow function', () => {
      const { result } = renderHook(() => useFormatTimeFromNow())

      expect(result.current).toHaveProperty('formatTimeFromNow')
      expect(typeof result.current.formatTimeFromNow).toBe('function')
    })

    /**
     * Test basic relative time formatting with English locale
     * Should return human-readable relative time strings
     */
    it('should format time from now in English', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'en-US' })

      const { result } = renderHook(() => useFormatTimeFromNow())

      const now = Date.now()
      const oneHourAgo = now - (60 * 60 * 1000)
      const formatted = result.current.formatTimeFromNow(oneHourAgo)

      // Should contain "hour" or "hours" and "ago"
      expect(formatted).toMatch(/hour|hours/)
      expect(formatted).toMatch(/ago/)
    })

    /**
     * Test that recent times are formatted as "a few seconds ago"
     * Very recent timestamps should show seconds
     */
    it('should format very recent times', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'en-US' })

      const { result } = renderHook(() => useFormatTimeFromNow())

      const now = Date.now()
      const fiveSecondsAgo = now - (5 * 1000)
      const formatted = result.current.formatTimeFromNow(fiveSecondsAgo)

      expect(formatted).toMatch(/second|seconds|few seconds/)
    })

    /**
     * Test formatting of times in the past (days ago)
     * Should handle day-level granularity
     */
    it('should format times from days ago', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'en-US' })

      const { result } = renderHook(() => useFormatTimeFromNow())

      const now = Date.now()
      const threeDaysAgo = now - (3 * 24 * 60 * 60 * 1000)
      const formatted = result.current.formatTimeFromNow(threeDaysAgo)

      expect(formatted).toMatch(/day|days/)
      expect(formatted).toMatch(/ago/)
    })

    /**
     * Test formatting of future times
     * dayjs fromNow also supports future times (e.g., "in 2 hours")
     */
    it('should format future times', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'en-US' })

      const { result } = renderHook(() => useFormatTimeFromNow())

      const now = Date.now()
      const twoHoursFromNow = now + (2 * 60 * 60 * 1000)
      const formatted = result.current.formatTimeFromNow(twoHoursFromNow)

      expect(formatted).toMatch(/in/)
      expect(formatted).toMatch(/hour|hours/)
    })
  })

  describe('Locale support', () => {
    /**
     * Test Chinese (Simplified) locale formatting
     * Should use Chinese characters for time units
     */
    it('should format time in Chinese (Simplified)', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'zh-Hans' })

      const { result } = renderHook(() => useFormatTimeFromNow())

      const now = Date.now()
      const oneHourAgo = now - (60 * 60 * 1000)
      const formatted = result.current.formatTimeFromNow(oneHourAgo)

      // Chinese should contain Chinese characters
      expect(formatted).toMatch(/[\u4E00-\u9FA5]/)
    })

    /**
     * Test Spanish locale formatting
     * Should use Spanish words for relative time
     */
    it('should format time in Spanish', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'es-ES' })

      const { result } = renderHook(() => useFormatTimeFromNow())

      const now = Date.now()
      const oneHourAgo = now - (60 * 60 * 1000)
      const formatted = result.current.formatTimeFromNow(oneHourAgo)

      // Spanish should contain "hace" (ago)
      expect(formatted).toMatch(/hace/)
    })

    /**
     * Test French locale formatting
     * Should use French words for relative time
     */
    it('should format time in French', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'fr-FR' })

      const { result } = renderHook(() => useFormatTimeFromNow())

      const now = Date.now()
      const oneHourAgo = now - (60 * 60 * 1000)
      const formatted = result.current.formatTimeFromNow(oneHourAgo)

      // French should contain "il y a" (ago)
      expect(formatted).toMatch(/il y a/)
    })

    /**
     * Test Japanese locale formatting
     * Should use Japanese characters
     */
    it('should format time in Japanese', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'ja-JP' })

      const { result } = renderHook(() => useFormatTimeFromNow())

      const now = Date.now()
      const oneHourAgo = now - (60 * 60 * 1000)
      const formatted = result.current.formatTimeFromNow(oneHourAgo)

      // Japanese should contain Japanese characters
      expect(formatted).toMatch(/[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/)
    })

    /**
     * Test Portuguese (Brazil) locale formatting
     * Should use pt-br locale mapping
     */
    it('should format time in Portuguese (Brazil)', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'pt-BR' })

      const { result } = renderHook(() => useFormatTimeFromNow())

      const now = Date.now()
      const oneHourAgo = now - (60 * 60 * 1000)
      const formatted = result.current.formatTimeFromNow(oneHourAgo)

      // Portuguese should contain "há" (ago)
      expect(formatted).toMatch(/há/)
    })

    /**
     * Test fallback to English for unsupported locales
     * Unknown locales should default to English
     */
    it('should fallback to English for unsupported locale', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'xx-XX' as any })

      const { result } = renderHook(() => useFormatTimeFromNow())

      const now = Date.now()
      const oneHourAgo = now - (60 * 60 * 1000)
      const formatted = result.current.formatTimeFromNow(oneHourAgo)

      // Should still return a valid string (in English)
      expect(typeof formatted).toBe('string')
      expect(formatted.length).toBeGreaterThan(0)
    })
  })

  describe('Edge cases', () => {
    /**
     * Test handling of timestamp 0 (Unix epoch)
     * Should format as a very old date
     */
    it('should handle timestamp 0', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'en-US' })

      const { result } = renderHook(() => useFormatTimeFromNow())

      const formatted = result.current.formatTimeFromNow(0)

      expect(typeof formatted).toBe('string')
      expect(formatted.length).toBeGreaterThan(0)
      expect(formatted).toMatch(/year|years/)
    })

    /**
     * Test handling of very large timestamps
     * Should handle dates far in the future
     */
    it('should handle very large timestamps', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'en-US' })

      const { result } = renderHook(() => useFormatTimeFromNow())

      const farFuture = Date.now() + (365 * 24 * 60 * 60 * 1000) // 1 year from now
      const formatted = result.current.formatTimeFromNow(farFuture)

      expect(typeof formatted).toBe('string')
      expect(formatted).toMatch(/in/)
    })

    /**
     * Test that the function is memoized based on locale
     * Changing locale should update the function
     */
    it('should update when locale changes', () => {
      const { result, rerender } = renderHook(() => useFormatTimeFromNow())

      const now = Date.now()
      const oneHourAgo = now - (60 * 60 * 1000)

      // First render with English
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'en-US' })
      rerender()
      const englishResult = result.current.formatTimeFromNow(oneHourAgo)

      // Second render with Spanish
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'es-ES' })
      rerender()
      const spanishResult = result.current.formatTimeFromNow(oneHourAgo)

      // Results should be different
      expect(englishResult).not.toBe(spanishResult)
    })
  })

  describe('Time granularity', () => {
    /**
     * Test different time granularities (seconds, minutes, hours, days, months, years)
     * dayjs should automatically choose the appropriate unit
     */
    it('should use appropriate time units for different durations', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'en-US' })

      const { result } = renderHook(() => useFormatTimeFromNow())

      const now = Date.now()

      // Seconds
      const seconds = result.current.formatTimeFromNow(now - 30 * 1000)
      expect(seconds).toMatch(/second/)

      // Minutes
      const minutes = result.current.formatTimeFromNow(now - 5 * 60 * 1000)
      expect(minutes).toMatch(/minute/)

      // Hours
      const hours = result.current.formatTimeFromNow(now - 3 * 60 * 60 * 1000)
      expect(hours).toMatch(/hour/)

      // Days
      const days = result.current.formatTimeFromNow(now - 5 * 24 * 60 * 60 * 1000)
      expect(days).toMatch(/day/)

      // Months
      const months = result.current.formatTimeFromNow(now - 60 * 24 * 60 * 60 * 1000)
      expect(months).toMatch(/month/)
    })
  })

  describe('Locale mapping', () => {
    /**
     * Test that all supported locales in the localeMap are handled correctly
     * This ensures the mapping from app locales to dayjs locales works
     */
    it('should handle all mapped locales', () => {
      const locales = [
        'en-US', 'zh-Hans', 'zh-Hant', 'pt-BR', 'es-ES', 'fr-FR',
        'de-DE', 'ja-JP', 'ko-KR', 'ru-RU', 'it-IT', 'th-TH',
        'id-ID', 'uk-UA', 'vi-VN', 'ro-RO', 'pl-PL', 'hi-IN',
        'tr-TR', 'fa-IR', 'sl-SI',
      ]

      const now = Date.now()
      const oneHourAgo = now - (60 * 60 * 1000)

      locales.forEach((locale) => {
        ;(useI18N as jest.Mock).mockReturnValue({ locale })

        const { result } = renderHook(() => useFormatTimeFromNow())
        const formatted = result.current.formatTimeFromNow(oneHourAgo)

        // Should return a non-empty string for each locale
        expect(typeof formatted).toBe('string')
        expect(formatted.length).toBeGreaterThan(0)
      })
    })
  })

  describe('Performance', () => {
    /**
     * Test that the hook doesn't create new functions on every render
     * The formatTimeFromNow function should be memoized with useCallback
     */
    it('should memoize formatTimeFromNow function', () => {
      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'en-US' })

      const { result, rerender } = renderHook(() => useFormatTimeFromNow())

      const firstFunction = result.current.formatTimeFromNow
      rerender()
      const secondFunction = result.current.formatTimeFromNow

      // Same locale should return the same function reference
      expect(firstFunction).toBe(secondFunction)
    })

    /**
     * Test that changing locale creates a new function
     * This ensures the memoization dependency on locale works correctly
     */
    it('should create new function when locale changes', () => {
      const { result, rerender } = renderHook(() => useFormatTimeFromNow())

      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'en-US' })
      rerender()
      const englishFunction = result.current.formatTimeFromNow

      ;(useI18N as jest.Mock).mockReturnValue({ locale: 'es-ES' })
      rerender()
      const spanishFunction = result.current.formatTimeFromNow

      // Different locale should return different function reference
      expect(englishFunction).not.toBe(spanishFunction)
    })
  })
})
